// Mesh Format Loader JED
// (c) jimon game studio

#include "CMeshFormatLoader_JED.h"

#ifdef JE_SUPPORT_FILEFORMAT_JED

#include "IJEDManager.h"
#include "IVariables.h"

namespace je
{
	namespace scene
	{

		//! Constructor
		CMeshFormatLoader_JED::CMeshFormatLoader_JED()
		{
		}

		//! Destructor
		CMeshFormatLoader_JED::~CMeshFormatLoader_JED()
		{
		}

		//! Load Mesh
		jeMesh * CMeshFormatLoader_JED::LoadMesh(core::IBuffer * DataBuffer,video::IRender * Render)
		{
			core::jeJED_DataBlock * Root = core::GetJEDManager()->Load(DataBuffer);

			AttachBuffersListType AttachBuffersList;
			jeMesh * Mesh = LoadMesh(Root,AttachBuffersList,Render);

			for(AttachBuffersListType::Iterator Iter=AttachBuffersList.begin();Iter!=AttachBuffersList.end();Iter++)
				JEDELETE(*Iter)
			AttachBuffersList.clear();

			Root->Drop();

			return Mesh;
		}

		//! Save Mesh
		void CMeshFormatLoader_JED::SaveMesh(core::IBuffer * DataBuffer,jeMesh * Mesh)
		{
			core::jeJED_DataBlock * Root = core::GetJEDManager()->CreateDataBlock(core::JEDDBT_NULL);
			Root->SetName("RootBlock");

			SavedListType SavedList;

			SaveMesh(Mesh,SavedList,Root);

			for(SavedListType::Iterator Iter = SavedList.begin();Iter!=SavedList.end();Iter++)
				JEDELETE(*Iter)
			SavedList.clear();

			core::GetJEDManager()->Save(DataBuffer,Root);
			Root->Drop();
		}

		//! Return true if mesh loader can work with this data
		u1 CMeshFormatLoader_JED::CanWorkWithThis(core::IBuffer * DataBuffer)
		{
			return core::GetJEDManager()->IsCanLoadThis(DataBuffer);
		}

		ISurface * CMeshFormatLoader_JED::LoadSurface(core::jeJED_SurfaceBlock * Data,AttachBuffersListType & AttachBuffersList,video::IRender * Render)
		{
			video::IHardwareBuffer * VB = NULL;
			video::IHardwareBuffer * IB = NULL;

			s32 VBSize = 0,IBSize = 0;

			for(core::jeJED_SurfaceBlock::DataBlockListType::Iterator Iter = Data->DataList.begin();Iter!=Data->DataList.end();Iter++)
				switch((*Iter)->Id)
				{
				case 1:
				case 3:
				case 4:
					VBSize += (*Iter)->Block_Size;
					break;
				case 5:
					VBSize += (*Iter)->Block_Size-2;
					break;
				case 2:
					IBSize += (*Iter)->Block_Size-1;
					break;
				}

			if(VBSize > 0)
			{
				VB = Render->CreateHardwareBuffer(video::G3DT_VERTEX);
				VB->Lock(core::BLT_WRITE);
				VB->Allocate(VBSize);
				VB->SetStreamWorkMode(true);
			}

			if(IBSize > 0)
			{
				IB = Render->CreateHardwareBuffer(video::G3DT_INDEX);
				IB->Lock(core::BLT_WRITE);
				IB->Allocate(IBSize);
				IB->SetStreamWorkMode(true);
			}

			AttachBuffersListType CurrentList;

			#define SETUP_LAYER_ALTER(__Id,__Buff,__Layer,__VarPerObj,__VarSize,__AlterCode)\
				case __Id:\
				{\
					AttachBuffers * Att = NULL;\
					JENEW(Att,AttachBuffers)\
					CurrentList.push_back(Att);\
					Att->Id = __Id;\
					Att->Buffer = __Buff;\
					Att->Layer = __Layer;\
					Att->VariablesPerObject = __VarPerObj;\
					Att->VariablesSize = __VarSize;\
					Att->Path = Data->GetFullPath();\
					/*We have global list of VBO's, so to add new data to VBO we need to find out what size of block already there*/ \
					for(AttachBuffersListType::Iterator AttIter=AttachBuffersList.begin();AttIter!=AttachBuffersList.end();AttIter++)\
						if((*AttIter)->Buffer == __Buff)\
						{\
							/*there we get offset, and set current stride = offset because below we set new stride to all entrys*/ \
							Att->Stride = Att->Offset = (*AttIter)->Stride;\
							break;\
						}\
					/*add our data config to global list*/ \
					AttachBuffersList.push_back(Att);\
					/*set new stride to all objects*/ \
					for(AttachBuffersListType::Iterator AttIter=AttachBuffersList.begin();AttIter!=AttachBuffersList.end();AttIter++)\
						if((*AttIter)->Buffer == __Buff)\
							(*AttIter)->Stride+=Att->VariablesPerObject*Att->VariablesSize;\
					__AlterCode\
					break;\
				}
			#define SETUP_DEFAULT(__Buff) __Buff->Merge((*Iter)->Data,(*Iter)->Block_Size,Att->VariablesPerObject*Att->VariablesSize,Att->Stride);
			#define SETUP_LAYER(__Id,__Buff,__Layer,__VarPerObj,__VarSize) SETUP_LAYER_ALTER(__Id,__Buff,__Layer,__VarPerObj,__VarSize,SETUP_DEFAULT(__Buff))
			#define SETUP_INDEX IB->Merge((*Iter)->Data+1,(*Iter)->Block_Size-1,Att->VariablesPerObject*Att->VariablesSize,Att->Stride);
			#define SETUP_UV \
				u8 LayerCount = (*Iter)->Data[0];\
				u8 UVW_Or_UV = (*Iter)->Data[1];\
				if(UVW_Or_UV == 1)\
				{\
					u32 VertexCount = ((*Iter)->Block_Size-2)/(LayerCount*3*4);\
					u8 * UV = NULL;\
					JENEW_A(UV,u8,VertexCount*8)\
					core::RemapArray((*Iter)->Data+2,3*4,VertexCount,UV,2*4);\
					VB->Merge(UV,VertexCount*8,Att->VariablesPerObject*Att->VariablesSize,Att->Stride);\
					JEDELETE_A(UV)\
				}\
				else if(UVW_Or_UV == 2)\
				{\
					u32 VertexCount = ((*Iter)->Block_Size-2)/(LayerCount*2*4);\
					VB->Merge((*Iter)->Data+2,VertexCount*8,Att->VariablesPerObject*Att->VariablesSize,Att->Stride);\
				}

			for(core::jeJED_SurfaceBlock::DataBlockListType::Iterator Iter = Data->DataList.begin();Iter!=Data->DataList.end();Iter++)
				switch((*Iter)->Id)
				{
					SETUP_LAYER(1,VB,video::HBAL_POSITION,3,4)
					SETUP_LAYER_ALTER(2,IB,video::HBAL_INDEXES,1,(*Iter)->Data[0],SETUP_INDEX)
					SETUP_LAYER(3,VB,video::HBAL_NORMAL,3,4)
					SETUP_LAYER(4,VB,video::HBAL_COLOR,4,4)
					SETUP_LAYER_ALTER(5,VB,video::HBAL_UV,2,4,SETUP_UV)
					case 6:
					{
						s32 Id = (*Iter)->Data[0];
						je_wchar_t * Temp = NULL;
						JENEW_A(Temp,je_wchar_t,((*Iter)->Block_Size-1)/2+1)
						memset(Temp,0,(*Iter)->Block_Size-1+2);
						memcpy(Temp,(*Iter)->Data+1,(*Iter)->Block_Size-1);
						jeStringw Path = Temp;
						JEDELETE_A(Temp)
						for(AttachBuffersListType::Iterator AttIter=AttachBuffersList.begin();AttIter!=AttachBuffersList.end();AttIter++)
							if(((*AttIter)->Path == Path) && ((*AttIter)->Id == Id))
							{
								CurrentList.push_back(*AttIter);
								break;
							}
						break;
					}
				}

			if(VB)
				VB->UnLock();

			if(IB)
				IB->UnLock();

			video::IHardwareBufferArray * Arr = Render->CreateHardwareBufferArray();
			for(AttachBuffersListType::Iterator Iter=CurrentList.begin();Iter!=CurrentList.end();Iter++)
				Arr->AttachBuffer((*Iter)->Buffer,(*Iter)->Layer,(*Iter)->VariablesPerObject,(*Iter)->VariablesSize,(*Iter)->Stride ,(*Iter)->Offset);
			CurrentList.clear();

			if(VB)
				VB->Drop();

			if(IB)
				IB->Drop();

			video::jeRenderOperation_Draw * Op = NULL;
			JENEW(Op,video::jeRenderOperation_Draw)

			Op->SetArray(Arr);
			Arr->Drop();

			video::RenderOperationType Type;

			switch(Data->Draw_Operation_Type)
			{
			case core::jeJED_SurfaceBlock::DOTL_TRIANGLES:
				Type = video::ROT_DRAW_TRIANGLES;
				break;
			case core::jeJED_SurfaceBlock::DOTL_TRIANGLE_STRIP:
				Type = video::ROT_DRAW_TRIANGLE_STRIP;
				break;
			case core::jeJED_SurfaceBlock::DOTL_LINES:
				Type = video::ROT_DRAW_LINES;
				break;
			case core::jeJED_SurfaceBlock::DOTL_POINTS:
				Type = video::ROT_DRAW_POINTS;
				break;
			}

			ISurface * Surface = CreateSurface(Render,Type,Op);
			Op->Drop();

			u1 UseUniqueMaterial = core::GetVariables()->GetVariablec8("SceneManager_SufracesDefaultUseUniqueMaterial")?true:false;
			u1 UseUniqueTexture = core::GetVariables()->GetVariablec8("SceneManager_SufracesDefaultUseUniqueTexture")?true:false;
			u1 UseUniqueShader = core::GetVariables()->GetVariablec8("SceneManager_SufracesDefaultUseUniqueShader")?true:false;

			Surface->SetMaterialResource(JE_MATERIALRESOURCE(Data->MaterialName.c_str()),UseUniqueMaterial,UseUniqueTexture,UseUniqueShader);

			return Surface;
		}

		jeMesh * CMeshFormatLoader_JED::LoadMesh(core::jeJED_DataBlock * Data,AttachBuffersListType & AttachBuffersList,video::IRender * Render)
		{
			jeMesh * Current = NULL;
			if(Data->GetId() == core::JEDDBT_MESH)
			{
				JENEW(Current,jeMesh)
				core::jeJED_MeshBlock * Mesh = static_cast<core::jeJED_MeshBlock*>(Data);
				Current->SetName(Mesh->GetMeshName());
			}

			jeList<ISurface*> ReturnSurfaces;
			jeList<jeMesh*> ReturnMeshes;

			for(core::jeJED_DataBlock::ChildrensListType::Iterator Iter = Data->GetChildrens().begin();Iter != Data->GetChildrens().end();Iter++)
			{
				switch((*Iter)->GetId())
				{
				case core::JEDDBT_SURFACE:
					ReturnSurfaces.push_back(LoadSurface(static_cast<core::jeJED_SurfaceBlock*>(*Iter),AttachBuffersList,Render));
					break;
				case core::JEDDBT_MESH:
					ReturnMeshes.push_back(LoadMesh(*Iter,AttachBuffersList,Render));
					break;
				}
			}

			if(Current)
				for(jeList<ISurface*>::Iterator Iter = ReturnSurfaces.begin();Iter!=ReturnSurfaces.end();Iter++)
					Current->AddSurface(*Iter);

			for(jeList<ISurface*>::Iterator Iter = ReturnSurfaces.begin();Iter!=ReturnSurfaces.end();Iter++)
				(*Iter)->Drop();
			ReturnSurfaces.clear();

			if(Current)
				for(jeList<jeMesh*>::Iterator Iter = ReturnMeshes.begin();Iter!=ReturnMeshes.end();Iter++)
					Current->AddChildren(*Iter);
			else
				if(ReturnMeshes.getSize() == 1)
				{
					Current = *ReturnMeshes.getLast();
					Current->Grab();
				}
				else
				{
					JENEW(Current,jeMesh)
					Current->SetName("Temp");
					for(jeList<jeMesh*>::Iterator Iter = ReturnMeshes.begin();Iter!=ReturnMeshes.end();Iter++)
						Current->AddChildren(*Iter);
				}

			for(jeList<jeMesh*>::Iterator Iter = ReturnMeshes.begin();Iter!=ReturnMeshes.end();Iter++)
				(*Iter)->Drop();
			ReturnMeshes.clear();

			return Current;
		}


		void CMeshFormatLoader_JED::SaveSurfaces(jeMesh::SurfacesListType & Surfaces,SavedListType & SavedList,core::jeJED_DataBlock * ParentData)
		{
			for(jeMesh::SurfacesListType::Iterator Iter = Surfaces.begin();Iter!=Surfaces.end();Iter++)
			{
				core::jeJED_SurfaceBlock * SurfaceBlock = static_cast<core::jeJED_SurfaceBlock*>(core::GetJEDManager()->CreateDataBlock(core::JEDDBT_SURFACE));

				ParentData->AddChildren(SurfaceBlock);
				SurfaceBlock->SetName(jeStringw("Surface_")+jeStringw(ParentData->GetChildrens().getSize()));

				if((*Iter)->GetMaterialResource())
					SurfaceBlock->MaterialName = (*Iter)->GetMaterialResource()->GetName().c_str();

				switch((*Iter)->GetRenderOperationType())
				{
				case video::ROT_DRAW_TRIANGLES:
					SurfaceBlock->Draw_Operation_Type = core::jeJED_SurfaceBlock::DOTL_TRIANGLES;
					break;
				case video::ROT_DRAW_TRIANGLE_STRIP:
					SurfaceBlock->Draw_Operation_Type = core::jeJED_SurfaceBlock::DOTL_TRIANGLE_STRIP;
					break;
				case video::ROT_DRAW_LINES:
					SurfaceBlock->Draw_Operation_Type = core::jeJED_SurfaceBlock::DOTL_LINES;
					break;
				case video::ROT_DRAW_POINTS:
					SurfaceBlock->Draw_Operation_Type = core::jeJED_SurfaceBlock::DOTL_POINTS;
					break;
				default:
					break;
				}

				video::IHardwareBufferArray * Array = static_cast<video::jeRenderOperation_Draw*>((*Iter)->GetRenderOperation())->Array;

				video::IHardwareBufferArray::LayerInfo & IPos = Array->GetLayerInfo(video::HBAL_POSITION);
				video::IHardwareBufferArray::LayerInfo & NPos = Array->GetLayerInfo(video::HBAL_NORMAL);
				video::IHardwareBufferArray::LayerInfo & Color = Array->GetLayerInfo(video::HBAL_COLOR);
				video::IHardwareBufferArray::LayerInfo & UV = Array->GetLayerInfo(video::HBAL_UV);
				video::IHardwareBufferArray::LayerInfo & Index = Array->GetLayerInfo(video::HBAL_INDEXES);

				JEASSERT(IPos.Buffer)
				JEASSERT(Index.Buffer)

				u32 ObjectCount = (u32)IPos.Buffer->GetSize() / IPos.Stride;
				u32 IndexCount = (u32)Index.Buffer->GetSize() / Index.VariableSize;

				#define CHECK_ALREADY_SAVED(__LayerInfo)\
					u1 EqualSavedFlag = false;\
					for(SavedListType::Iterator CIter = SavedList.begin();CIter!=SavedList.end();CIter++)\
					{\
						if((*CIter)->Equal(&__LayerInfo))\
						{\
							core::jeJED_SurfaceBlock::DataBlock * DB = NULL;\
							JENEW(DB,core::jeJED_SurfaceBlock::DataBlock)\
							SurfaceBlock->DataList.push_back(DB);\
							DB->Id = 6;\
							DB->Block_Size = 1+(*CIter)->Path.GetSize()*2;\
							JENEW_A(DB->Data,u8,DB->Block_Size)\
							DB->Data[0] = (*CIter)->DataBlockId;\
							memcpy(DB->Data+1,(*CIter)->Path.c_str(),(*CIter)->Path.GetSize()*2);\
							EqualSavedFlag = true;\
							break;\
						}\
					}
				#define IF_NEED_TO_SAVE if(!EqualSavedFlag)
				#define SAVE_LAYERINFO(__LayerInfo,__Id)\
					SavedSurfaceDataBlocksInfo * BlockInfo = NULL;\
					JENEW(BlockInfo,SavedSurfaceDataBlocksInfo)\
					BlockInfo->DataBlockId = __Id;\
					BlockInfo->LayerInfo = &__LayerInfo;\
					BlockInfo->Path = SurfaceBlock->GetFullPath();\
					SavedList.push_back(BlockInfo);
				#define SAVE_SURFACE_LAYER(__LayerInfo,__Id,__Count)\
					if(__LayerInfo.Buffer)\
					{\
						CHECK_ALREADY_SAVED(__LayerInfo)\
						IF_NEED_TO_SAVE\
						{\
							u32 ObjSize = __LayerInfo.VariableSize*__LayerInfo.VariablesPerObject;\
							core::jeJED_SurfaceBlock::DataBlock * DB = NULL;\
							JENEW(DB,core::jeJED_SurfaceBlock::DataBlock)\
							SurfaceBlock->DataList.push_back(DB);\
							DB->Id = __Id;\
							DB->Block_Size = __Count*ObjSize;\
							JENEW_A(DB->Data,u8,DB->Block_Size)\
							__LayerInfo.Buffer->Lock(core::BLT_READ);\
							__LayerInfo.Buffer->UnMerge(DB->Data,DB->Block_Size,ObjSize,__LayerInfo.Stride,__LayerInfo.Offset);\
							__LayerInfo.Buffer->UnLock();\
							SAVE_LAYERINFO(__LayerInfo,__Id)\
						}\
					}
				#define SAVE_SURFACE_LAYER_ALTER(__LayerInfo,__Id,__Size,__AlterCode)\
					if(__LayerInfo.Buffer)\
					{\
						CHECK_ALREADY_SAVED(__LayerInfo)\
						IF_NEED_TO_SAVE\
						{\
							u32 ObjSize = __LayerInfo.VariableSize*__LayerInfo.VariablesPerObject;\
							core::jeJED_SurfaceBlock::DataBlock * DB = NULL;\
							JENEW(DB,core::jeJED_SurfaceBlock::DataBlock)\
							SurfaceBlock->DataList.push_back(DB);\
							DB->Id = __Id;\
							DB->Block_Size = __Size;\
							JENEW_A(DB->Data,u8,DB->Block_Size)\
							__LayerInfo.Buffer->Lock(core::BLT_READ);\
							__AlterCode\
							__LayerInfo.Buffer->UnLock();\
							SAVE_LAYERINFO(__LayerInfo,__Id)\
						}\
					}

				#define SAVE_INDEXES DB->Data[0] = Index.VariableSize;\
					Index.Buffer->UnMerge(DB->Data+1,DB->Block_Size-1,ObjSize,Index.Stride,Index.Offset);

				#define SAVE_UV DB->Data[0] = 1;\
						DB->Data[1] = 2;\
						UV.Buffer->UnMerge(DB->Data+2,ObjectCount*ObjSize,ObjSize,UV.Stride,UV.Offset);

				SAVE_SURFACE_LAYER(IPos,1,ObjectCount)
				SAVE_SURFACE_LAYER_ALTER(Index,2,IndexCount*ObjSize+1,SAVE_INDEXES)
				SAVE_SURFACE_LAYER(NPos,3,ObjectCount)
				SAVE_SURFACE_LAYER(Color,4,ObjectCount)
				SAVE_SURFACE_LAYER_ALTER(UV,5,ObjectCount*2*4+2,SAVE_UV)

				SurfaceBlock->Drop();
			}
		}

		void CMeshFormatLoader_JED::SaveMesh(jeMesh * Mesh,SavedListType & SavedList,core::jeJED_DataBlock * ParentData)
		{
			core::jeJED_MeshBlock * MeshBlock = static_cast<core::jeJED_MeshBlock*>(core::GetJEDManager()->CreateDataBlock(core::JEDDBT_MESH));
			MeshBlock->SetMeshName(Mesh->GetName());

			ParentData->AddChildren(MeshBlock);
			MeshBlock->SetName(jeStringw("Mesh_")+jeStringw(ParentData->GetChildrens().getSize()));

			for(jeMesh::MeshListType::Iterator Iter = Mesh->GetMeshes().begin();Iter != Mesh->GetMeshes().end();Iter++)
				SaveMesh(*Iter,SavedList,MeshBlock);

			SaveSurfaces(Mesh->GetSurfaces(),SavedList,MeshBlock);

			MeshBlock->Drop();
		}


	}
}

#endif
